package org.libarae.communication;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;

import javax.imageio.ImageIO;
import javax.xml.parsers.ParserConfigurationException;

import org.libarae.communication.request.LoginRequest;
import org.libarae.communication.request.Request;
import org.libarae.communication.response.LoginResponse;
import org.libarae.communication.response.PictureResponse;
import org.libarae.communication.response.Response;
import org.libarae.communication.response.WebLoginPageResponse;
import org.xml.sax.SAXException;

public class ServerInterface {
	private Socket mSocket;						//main socket for the session
	private boolean mUserIsWebClient = false;	//to see if the user is a web client

	public ServerInterface(Socket s){
		mSocket = s;
	}
	
	// Returns a Request object or null if unsuccessful
	public Request getRequest() throws IOException, SAXException, ParserConfigurationException, XmlParseException{
		//set a time out
		mSocket.setSoTimeout(9000);
		
		//check the socket
		if(mSocket == null || mSocket.isClosed()){
			throw new IOException("Socket is null or closed");
		}
		
		//get the input stream
		BufferedReader buffIn = new BufferedReader(new InputStreamReader(mSocket.getInputStream()));
		
		//dump the String
		StringBuilder strBuild = new StringBuilder();
		String temp;
		
		//First line read separately so we know when to stop for HTTP requests
		temp = buffIn.readLine();
		strBuild.append(temp).append('\n'); 
		mUserIsWebClient = temp.startsWith("GET") || temp.startsWith("POST");
		boolean webIsGet = temp.startsWith("GET");
		
		char[] postData = null;
		//Now read the rest of the message
		while((temp = buffIn.readLine()) != null){
			if(temp.equals(ClientInterface.END_RESPONSE_STRING))
				break;
			if(temp.equals("") && mUserIsWebClient){
				if(webIsGet)
					break;
				else{
					buffIn.read(postData);
					strBuild.append("POSTDATA:"+(new String(postData))).append('\n'); //add my own header to find easily
					break;	
				}
			}
			strBuild.append(temp).append('\n'); 
			if(temp.contains("Content-Length:")){ //here we need to get the POST data
				int length = Integer.parseInt(temp.split(":")[1].trim());
				postData = new char[length+2];			
			}
		}

		//test to see if it is a web or client request
		String message = strBuild.toString();

		if(mUserIsWebClient){
			//we have a web request
			Request rq;
			if(webIsGet)
				rq = GETMessageHandler.parseGET(message);
			else
				rq = parseLoginPOST(message);
			
			if(rq == null)//Special case, all non valid pages get sent to a login screen. 
			{
				sendResponse(new WebLoginPageResponse());
				return null;//Return null so that the caller does not do anything with the response. 
			}
			else//We need to obtain the mAccessKey value from the cookie we set
			{
				rq.setWebRequest(true);
				int startIndex = message.indexOf("mAccessKey");
				if(startIndex == -1)//Probably the brower requestion favicon.ico
					return rq;
				int endIndex = message.indexOf("\n", startIndex);
				
				rq.setAccessKey(message.substring(startIndex, endIndex).replaceAll(".*=", ""));
			}
			return rq;			
		}
		else{
			//this is a normal client call
			//get the object
			Request request =  RequestXmlHandler.parseXML(message);
			request.setWebRequest(false);
			return request;
		}		
	}
	
	// Takes a Response object to send back to the user 
	public boolean sendResponse(Response response) throws IOException, XmlParseException{
		//check the socket
		if(mSocket == null || mSocket.isClosed()){
			throw new IOException("Socket is null or closed");
		}
		
		//check the param
		if(response == null){
			mSocket.close();
			return false;
		}
		
		if(mUserIsWebClient){			
			OutputStream outStream = mSocket.getOutputStream();
			PrintWriter pWriter = new PrintWriter(outStream);
			//Naive server responses, we will always return a page
			pWriter.print("HTTP/1.0 200 OK\r\n");
			pWriter.print("Connection: close\r\n");//We dont want to do a persistent connection
			pWriter.print("Server: LibareaServer\r\n");//Name
			if(response instanceof PictureResponse)
				pWriter.print("Content-Type: image/jpg\r\n");//We will return an image in this case
			else//Default is an HTML page
				pWriter.print("Content-Type: text/html\r\n");//We will always repsone with an HTML page
			if(response instanceof LoginResponse)//Set a cookie for the acess key
				pWriter.print("Set-Cookie: mAccessKey=" +HTTPUtils.encodeURL(((LoginResponse)response).getAcessKey()) + "\r\n"); 
			pWriter.print("\r\n");//End of header, after is the content
			
			if(response instanceof PictureResponse)
			{//We need to write a byte stream
				pWriter.flush();//Empty the buffer of pWriter before we start writing dirrectly to the underlying stream
				ImageIO.write(((PictureResponse)response).getImage(), "jpg", outStream );
			}
			else
				pWriter.print(response.createHTML());
			pWriter.flush();
			pWriter.close();
		}
		else{
			//create the xml to send
			String xmlToSend = ResponseXmlHandler.createXML(response);
			
			//get the output
			PrintWriter pWriter = new PrintWriter(mSocket.getOutputStream());
			//print the output
			pWriter.println(xmlToSend);
			//flush and close
			pWriter.flush();
			pWriter.close();
		}
		
		//close the socket
		mSocket.close();		
		return true;
	}
	
	public boolean isSocketClosed(){
		return mSocket.isClosed();
	}
	
	//here we only handle post commands from login
	private Request parseLoginPOST(String message){
		if(message.startsWith("POST /login.html")){
			message = message.substring(message.indexOf("POSTDATA:"));
			String userName = message.substring(message.indexOf('=')+1, message.indexOf('&'));
			String password = message.substring(message.lastIndexOf('=')+1, message.lastIndexOf("\n")).trim();
			return new LoginRequest(userName, LoginRequest.getMd5(password));
		}
		try {
			mSocket.close(); //try to close the the socket
		} catch (IOException e) {} 
		return null; //return null to the server, this will be ingored
	}
}

